/*
 * TeleStax, Open Source Cloud Communications  Copyright 2012. 
 * and individual contributors
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.mobicents.protocols.ss7.isup.impl;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

import javolution.util.ReentrantLock;

import org.mobicents.protocols.ss7.isup.ISUPEvent;
import org.mobicents.protocols.ss7.isup.ISUPTimeoutEvent;
import org.mobicents.protocols.ss7.isup.ParameterException;
import org.mobicents.protocols.ss7.isup.impl.message.AbstractISUPMessage;
import org.mobicents.protocols.ss7.isup.message.AddressCompleteMessage;
import org.mobicents.protocols.ss7.isup.message.BlockingAckMessage;
import org.mobicents.protocols.ss7.isup.message.BlockingMessage;
import org.mobicents.protocols.ss7.isup.message.CircuitGroupBlockingMessage;
import org.mobicents.protocols.ss7.isup.message.CircuitGroupQueryMessage;
import org.mobicents.protocols.ss7.isup.message.CircuitGroupQueryResponseMessage;
import org.mobicents.protocols.ss7.isup.message.CircuitGroupResetAckMessage;
import org.mobicents.protocols.ss7.isup.message.CircuitGroupResetMessage;
import org.mobicents.protocols.ss7.isup.message.CircuitGroupUnblockingAckMessage;
import org.mobicents.protocols.ss7.isup.message.CircuitGroupUnblockingMessage;
import org.mobicents.protocols.ss7.isup.message.ConnectMessage;
import org.mobicents.protocols.ss7.isup.message.ISUPMessage;
import org.mobicents.protocols.ss7.isup.message.InformationMessage;
import org.mobicents.protocols.ss7.isup.message.InformationRequestMessage;
import org.mobicents.protocols.ss7.isup.message.InitialAddressMessage;
import org.mobicents.protocols.ss7.isup.message.ReleaseCompleteMessage;
import org.mobicents.protocols.ss7.isup.message.ReleaseMessage;
import org.mobicents.protocols.ss7.isup.message.ResetCircuitMessage;
import org.mobicents.protocols.ss7.isup.message.SubsequentAddressMessage;
import org.mobicents.protocols.ss7.isup.message.UnblockingAckMessage;
import org.mobicents.protocols.ss7.isup.message.UnblockingMessage;
import org.mobicents.protocols.ss7.isup.message.parameter.CauseIndicators;
import org.mobicents.protocols.ss7.mtp.Mtp3;
import org.mobicents.protocols.ss7.mtp.Mtp3TransferPrimitive;
import org.mobicents.protocols.ss7.mtp.Mtp3TransferPrimitiveFactory;
import org.mobicents.protocols.ss7.scheduler.ConcurrentLinkedList;
import org.mobicents.protocols.ss7.scheduler.Scheduler;
import org.mobicents.protocols.ss7.scheduler.Task;

/**
 *
 * @author baranowb
 * @author amit bhayani
 * 
 */
@SuppressWarnings("rawtypes")
class Circuit {
	private final int cic;
	private final int dpc;
	private final ISUPProviderImpl provider;
	
	private ReentrantLock lock = new ReentrantLock();
	
	private ConcurrentLinkedList<ISUPMessage> incoming = new ConcurrentLinkedList<ISUPMessage>();
	private ConcurrentLinkedList<ISUPMessage> outgoing = new ConcurrentLinkedList<ISUPMessage>();
	
	private Sender sender;
	private Receiver receiver;
	
	private ByteArrayOutputStream bos = new ByteArrayOutputStream(300);

	/**
	 * @param cic
	 */
	public Circuit(int cic, int dpc,ISUPProviderImpl provider,Scheduler scheduler) {
		sender=new Sender(scheduler);
		receiver=new Receiver(scheduler);
		this.cic = cic;
		this.dpc = dpc;
		this.provider = provider;
		
		//create all timers and deactivate them
		t1=new TimerT1(scheduler);
		t5=new TimerT5(scheduler);
		t7=new TimerT7(scheduler);
		t12=new TimerT12(scheduler);
		t13=new TimerT13(scheduler);
		t14=new TimerT14(scheduler);
		t15=new TimerT15(scheduler);
		t16=new TimerT16(scheduler);
		t17=new TimerT17(scheduler);
		t18=new TimerT18(scheduler);
		t19=new TimerT19(scheduler);
		t20=new TimerT20(scheduler);
		t21=new TimerT21(scheduler);		
		t22=new TimerT22(scheduler);
		t23=new TimerT23(scheduler);
		t28=new TimerT28(scheduler);
		t33=new TimerT33(scheduler);
		onStop();
	}
	
	/**
	 * @param timerId
	 * @return
	 */
	public boolean cancelTimer(int timerId) {
		try {

			lock.lock();
			switch (timerId) {
			case ISUPTimeoutEvent.T1:
				return cancelT1();
			case ISUPTimeoutEvent.T5:
				return cancelT5();
			case ISUPTimeoutEvent.T7:
				return cancelT7();
			case ISUPTimeoutEvent.T12:
				return cancelT12();
			case ISUPTimeoutEvent.T13:
				return cancelT13();
			case ISUPTimeoutEvent.T14:
				return cancelT14();
			case ISUPTimeoutEvent.T15:
				return cancelT15();
			case ISUPTimeoutEvent.T16:
				return cancelT16();
			case ISUPTimeoutEvent.T17:
				return cancelT17();
			case ISUPTimeoutEvent.T18:
				return cancelT18();
			case ISUPTimeoutEvent.T19:
				return cancelT19();
			case ISUPTimeoutEvent.T20:
				return cancelT20();
			case ISUPTimeoutEvent.T21:
				return cancelT21();
			case ISUPTimeoutEvent.T22:
				return cancelT22();
			case ISUPTimeoutEvent.T23:
				return cancelT23();
			case ISUPTimeoutEvent.T28:
				return cancelT28();
			case ISUPTimeoutEvent.T33:
				return cancelT33();
			default:
				return false;
			}
		} finally {
			lock.unlock();
		}
	}

	/**
	 * @param message
	 */
	public void receive(ISUPMessage message) {
		incoming.offer(message);
		receiver.submit();		
	}

	/**
	 * @param message
	 * @throws ParameterException
	 * @throws IOException 
	 */
	public void send(ISUPMessage message) throws ParameterException, IOException {
		outgoing.offer(message);
		sender.submit();		
	}
	
	/**
	 * @param message
	 * @return
	 * @throws ParameterException 
	 * @throws IOException 
	 */
	private Mtp3TransferPrimitive decorate(ISUPMessage message) throws ParameterException, IOException {
		((AbstractISUPMessage) message).encode(bos);
		byte[] encoded = bos.toByteArray();
		int opc = this.provider.getLocalSpc();
		int dpc = this.dpc;
		int si = Mtp3._SI_SERVICE_ISUP;
		int ni = this.provider.getNi();
		int sls = message.getSls() & 0x0F; //promote
//		int ssi = ni << 2;

	
//		ByteArrayOutputStream bout = new ByteArrayOutputStream();
//		// encoding routing label
//		bout.write((byte) (((ssi & 0x0F) << 4) | (si & 0x0F)));
//		bout.write((byte) dpc);
//		bout.write((byte) (((dpc >> 8) & 0x3F) | ((opc & 0x03) << 6)));
//		bout.write((byte) (opc >> 2));
//		bout.write((byte) (((opc >> 10) & 0x0F) | ((sls & 0x0F) << 4)));
//		bout.write(encoded);
//		byte[] msg = bout.toByteArray();

		Mtp3TransferPrimitiveFactory factory = this.provider.stack.getMtp3UserPart().getMtp3TransferPrimitiveFactory();
		Mtp3TransferPrimitive msg = factory.createMtp3TransferPrimitive(si, ni, 0, opc, dpc, sls, encoded);
		return msg;
	}	

	// --------------- data handlers  ---------------------------
	private class Receiver extends Task
	{
		public Receiver(Scheduler scheduler)
		{
			super(scheduler);
		}
		
		public int getQueueNumber()
		{
			return scheduler.L4READ_QUEUE;
		}
		
		public void submit()
		{
			scheduler.submit(this,getQueueNumber());
		}
		
		public long perform()		
		{
			lock.lock();			
			ISUPMessage message;
			while(!incoming.isEmpty())
			{
				message = incoming.poll();
				try {
					// process timers
					switch (message.getMessageType().getCode()) {
						// FIXME: add check for SEG
						case ReleaseCompleteMessage.MESSAGE_CODE:
							// this is tricy BS.... its REL and RSC are answered with RLC
							if (!stopRELTimers()) {
								stopRSCTimers();
							}
							break;
						case AddressCompleteMessage.MESSAGE_CODE:
						case ConnectMessage.MESSAGE_CODE:
							stoptXAMTimers();
							break;
						case BlockingAckMessage.MESSAGE_CODE:
							stoptBLOTimers();
							break;
						case UnblockingAckMessage.MESSAGE_CODE:
							stoptUBLTimers();
							break;
						case CircuitGroupBlockingMessage.MESSAGE_CODE:
							stoptCGBTimers();
							break;
			
						case CircuitGroupUnblockingAckMessage.MESSAGE_CODE:
							stoptCGUTimers();
							break;
						case CircuitGroupResetAckMessage.MESSAGE_CODE:
							stoptGRSTimers();
							break;
						case CircuitGroupQueryResponseMessage.MESSAGE_CODE:
							stoptCQMTimers();
							break;
						case InformationMessage.MESSAGE_CODE:
							stoptINRTimers();
							break;
					}
		
					// deliver
					ISUPEvent event = new ISUPEvent(provider, message , dpc);
					provider.deliver(event);
				} catch (Exception e) {
					// catch exception, so thread dont die.
					e.printStackTrace();
				} 							
			}
				
			lock.unlock();	
			return 0;
		}
	}
	
	private class Sender extends Task
	{
		public Sender(Scheduler scheduler)
		{
			super(scheduler);
		}
		
		public int getQueueNumber()
		{
			return scheduler.L4WRITE_QUEUE;
		}
		
		public void submit()
		{
			scheduler.submit(this,getQueueNumber());
		}
		
		public long perform()		
		{
			ISUPMessage message;
			while(!outgoing.isEmpty())
			{
				try {
					message=outgoing.poll();
					lock.lock();
					bos.reset();
					
					// FIXME: add SEG creation?
					Mtp3TransferPrimitive msg = decorate(message);
					// process timers
					switch (message.getMessageType().getCode()) {
						case ReleaseMessage.MESSAGE_CODE:
							startRELTimers(msg, (ReleaseMessage) message);
							break;
						case SubsequentAddressMessage.MESSAGE_CODE:
						case InitialAddressMessage.MESSAGE_CODE:
							startXAMTimers(message);
							break;
						case BlockingMessage.MESSAGE_CODE:
							startBLOTimers(msg, (BlockingMessage) message);
							break;
						case UnblockingMessage.MESSAGE_CODE:
							startUBLTimers(msg, (UnblockingMessage) message);
							break;
						case ResetCircuitMessage.MESSAGE_CODE:
							startRSCTimers(msg, (ResetCircuitMessage) message);
							break;
						case CircuitGroupBlockingMessage.MESSAGE_CODE:
							startCGBTimers(msg, (CircuitGroupBlockingMessage) message);
							break;
						case CircuitGroupUnblockingMessage.MESSAGE_CODE:
							startCGUTimers(msg, (CircuitGroupUnblockingMessage) message);
							break;
						case CircuitGroupResetMessage.MESSAGE_CODE:
							startGRSTimers(msg, (CircuitGroupResetMessage) message);
							break;
						case CircuitGroupQueryMessage.MESSAGE_CODE:
							startCQMTimers((CircuitGroupQueryMessage) message);
							break;
						case InformationRequestMessage.MESSAGE_CODE:
							startINRTimers((InformationRequestMessage) message);
							break;
					}
					// send
					provider.send(msg);
				}
				catch(Exception e) {
					e.printStackTrace();
				}
				finally {
					lock.unlock();
				}
			}
			
			return 0;
		}
	}
	
	// ----------------- timer handlers ----------------------	
	
	// FIXME: check how t3 works....

	private TimerT1 t1;
	private TimerT5 t5;
	private Mtp3TransferPrimitive t1t5encodedREL; // keep encoded value, so we can simply send,
									// without spending CPU on encoding.
	private ReleaseMessage t1t5REL; // keep for timers.
	private TimerT7 t7;
	private ISUPMessage t7AddressMessage; // IAM/SAM

	// FIXME: t8 - receive IAM with contuuity check ind.
	// FIXME: t11
	
	private TimerT12 t12;
	private TimerT13 t13;
	private Mtp3TransferPrimitive t12t13encodedBLO; // keep encoded value, so we can simply
										// send, without spending CPU on
										// encoding.
	private BlockingMessage t12t13BLO; // keep for timers.

	private TimerT14 t14;
	private TimerT15 t15;
	private Mtp3TransferPrimitive t14t15encodedUBL; // keep encoded value, so we can simply
										// send, without spending CPU on
										// encoding.
	private UnblockingMessage t14t15UBL; // keep for timers.

	private TimerT16 t16;
	private TimerT17 t17;
	private Mtp3TransferPrimitive t16t17encodedRSC; // keep encoded value, so we can simply
										// send, without spending CPU on
										// encoding.
	private ResetCircuitMessage t16t17RSC; // keep for timers.

	private TimerT18 t18;
	private TimerT19 t19;
	private Mtp3TransferPrimitive t18t19encodedCGB; // keep encoded value, so we can simply
										// send, without spending CPU on
										// encoding.
	private CircuitGroupBlockingMessage t18t19CGB; // keep for timers.

	private TimerT20 t20;
	private TimerT21 t21;
	private Mtp3TransferPrimitive t20t21encodedCGU; // keep encoded value, so we can simply
										// send, without spending CPU on
										// encoding.
	private CircuitGroupUnblockingMessage t20t21CGU; // keep for timers.

	private TimerT22 t22;
	private TimerT23 t23;
	private Mtp3TransferPrimitive t22t23encodedGRS; // keep encoded value, so we can simply
										// send, without spending CPU on
										// encoding.
	private CircuitGroupResetMessage t22t23GRS; // keep for timers.

	private TimerT28 t28;
	private CircuitGroupQueryMessage t28CQM;

	private TimerT33 t33;
	private InformationRequestMessage t33INR;

	// FIXME: t34 - check how SEG works

	private void startRELTimers(Mtp3TransferPrimitive encoded, ReleaseMessage rel) {
		// FIXME: add lock ?
		this.t1t5encodedREL = encoded;
		this.t1t5REL = rel;

		// it is started always.
		startT1();
		
		if (!this.t5.isActive()) {
			startT5();
		}
	}

	/**
	 * @return
	 */
	private boolean stopRELTimers() {
		if(this.t1.isActive() || this.t5.isActive()) {
			cancelT1();
			cancelT5();
			return true;
		} else {
			return false;
		}
	}

	/**
	 * @param encoded
	 * @param message
	 */
	private void startXAMTimers(ISUPMessage message) {
		this.cancelT7();
		this.t7AddressMessage = message;
		this.startT7();
	}

	/**
	 * @return
	 */
	private void stoptXAMTimers() {
		cancelT7();
	}

	/**
	 * @param encoded
	 * @param message
	 */
	private void startBLOTimers(Mtp3TransferPrimitive encoded, BlockingMessage message) {
		this.t12t13BLO = message;
		this.t12t13encodedBLO = encoded;
		// it is started always.
		startT12();

		if(!this.t13.isActive())
			startT13();		
	}

	/**
	 * @return
	 */
	private void stoptBLOTimers() {
		cancelT12();
		cancelT13();
	}

	/**
	 * @param encoded
	 * @param message
	 */
	private void startUBLTimers(Mtp3TransferPrimitive encoded, UnblockingMessage message) {
		this.t14t15UBL = message;
		this.t14t15encodedUBL = encoded;
		// it is started always.
		startT14();

		if(!this.t15.isActive())
			startT15();
	}

	/**
	 * @return
	 */
	private void stoptUBLTimers() {
		cancelT14();
		cancelT15();

	}

	/**
	 * @param encoded
	 * @param message
	 */
	private void startRSCTimers(Mtp3TransferPrimitive encoded, ResetCircuitMessage message) {
		this.t16t17RSC = message;
		this.t16t17encodedRSC = encoded;
		// it is started always.
		startT16();

		if(!this.t17.isActive())
			startT17();		
	}

	/**
	 * @return
	 */
	private void stopRSCTimers() {
		cancelT16();
		cancelT17();
	}

	/**
	 * @param encoded
	 * @param message
	 */
	private void startINRTimers(InformationRequestMessage message) {
		this.t33INR = message;
		startT33();
	}

	/**
	 * @return
	 */
	private void stoptINRTimers() {
		cancelT33();
	}

	/**
	 * @param encoded
	 * @param message
	 */
	private void startCQMTimers(CircuitGroupQueryMessage message) {
		this.t28CQM = message;

		// it is started always.
		startT28();
		// FIXME: can we send more than one?
	}

	/**
	 * @return
	 */
	private void stoptCQMTimers() {
		cancelT28();
	}

	/**
	 * @param encoded
	 * @param message
	 */
	private void startGRSTimers(Mtp3TransferPrimitive encoded, CircuitGroupResetMessage message) {
		this.t22t23GRS = message;
		this.t22t23encodedGRS = encoded;
		// it is started always.
		startT22();

		if(!this.t23.isActive())
			startT23();		
	}

	/**
	 * @return
	 */
	private void stoptGRSTimers() {
		cancelT22();
		cancelT23();
	}

	/**
	 * @param encoded
	 * @param message
	 */
	private void startCGUTimers(Mtp3TransferPrimitive encoded, CircuitGroupUnblockingMessage message) {
		this.t20t21CGU = message;
		this.t20t21encodedCGU = encoded;
		// it is started always.
		startT20();

		if(!this.t21.isActive())
			startT21();	
	}

	/**
	 * @return
	 */
	private void stoptCGUTimers() {
		cancelT20();
		cancelT21();
	}

	/**
	 * @param encoded
	 * @param message
	 */
	private void startCGBTimers(Mtp3TransferPrimitive encoded, CircuitGroupBlockingMessage message) {
		this.t18t19CGB = message;
		this.t18t19encodedCGB = encoded;
		// it is started always.
		startT18();

		if(!this.t19.isActive())
			startT19();
	}

	/**
	 * @return
	 */
	private void stoptCGBTimers() {
		cancelT18();
		cancelT19();
	}

	private void startT1() {
		cancelT1();
		this.t1.start();
	}

	private boolean cancelT1() {
		if(this.t1.isActive())
		{
			this.t1.cancel();
			return true;
		}
		
		return false;		
	}

	private void startT5() {
		cancelT5();
		this.t5.start();
	}

	private boolean cancelT5() {
		if(this.t5.isActive())
		{
			this.t5.cancel();
			return true;
		}
		
		return false;
	}

	private void startT7() {
		cancelT7();
		this.t7.start();
	}

	private boolean cancelT7() {
		if(this.t7.isActive())
		{
			this.t7.cancel();
			return true;
		}
		
		return false;
	}

	private void startT12() {
		cancelT12();
		this.t12.start();
	}

	private boolean cancelT12() {
		if(this.t12.isActive())
		{
			this.t12.cancel();
			return true;
		}
		
		return false;
	}

	private void startT13() {
		cancelT13();
		this.t13.start();
	}

	private boolean cancelT13() {
		if(this.t13.isActive())
		{
			this.t13.cancel();
			return true;
		}
		
		return false;
	}

	private void startT14() {
		cancelT14();
		this.t14.start();
	}

	private boolean cancelT14() {
		if(this.t14.isActive())
		{
			this.t14.cancel();
			return true;
		}
		
		return false;
	}

	private void startT15() {
		cancelT15();
		this.t15.start();
	}

	private boolean cancelT15() {
		if(this.t15.isActive())
		{
			this.t15.cancel();
			return true;
		}
		
		return false;
	}

	private void startT16() {
		cancelT16();
		this.t16.start();
	}

	private boolean cancelT16() {
		if(this.t16.isActive())
		{
			this.t16.cancel();
			return true;
		}
		
		return false;
	}

	private void startT17() {
		cancelT17();
		this.t17.start();
	}

	private boolean cancelT17() {
		if(this.t17.isActive())
		{
			this.t17.cancel();
			return true;
		}
		
		return false;
	}

	private void startT18() {
		cancelT18();
		this.t18.start();
	}

	private boolean cancelT18() {
		if(this.t18.isActive())
		{
			this.t18.cancel();
			return true;
		}
		
		return false;
	}

	private void startT19() {
		cancelT19();
		this.t19.start();
	}

	private boolean cancelT19() {
		if(this.t19.isActive())
		{
			this.t19.cancel();
			return true;
		}
		
		return false;
	}

	private void startT20() {
		cancelT20();
		this.t20.start();
	}

	private boolean cancelT20() {
		if(this.t20.isActive())
		{
			this.t20.cancel();
			return true;
		}
		
		return false;
	}

	private void startT21() {
		cancelT21();
		this.t21.start();
	}

	private boolean cancelT21() {
		if(this.t21.isActive())
		{
			this.t21.cancel();
			return true;
		}
		
		return false;
	}

	private void startT22() {
		cancelT22();
		this.t22.start();
	}

	private boolean cancelT22() {
		if(this.t22.isActive())
		{
			this.t22.cancel();
			return true;
		}
		
		return false;
	}

	private void startT23() {
		cancelT23();
		this.t23.start();
	}

	private boolean cancelT23() {
		if(this.t23.isActive())
		{
			this.t23.cancel();
			return true;
		}
		
		return false;
	}

	private void startT28() {
		cancelT28();
		this.t28.start();
	}

	private boolean cancelT28() {
		if(this.t28.isActive())
		{
			this.t28.cancel();
			return true;
		}
		
		return false;
	}

	private void startT33() {
		cancelT33();
		this.t33.start();
	}

	private boolean cancelT33() {
		if(this.t33.isActive())
		{
			this.t33.cancel();
			return true;
		}
		
		return false;
	}

	 private class TimerT1 extends Task  {
	    private int ttl;
	    	
	    public TimerT1(Scheduler scheduler)
	    {
	    	super(scheduler);
	    }
	    	
	    public int getQueueNumber()
	    {
	    	return scheduler.HEARTBEAT_QUEUE;
	    }
	    	
	    public void start()
	    {
	    	this.activate(true);
	    	ttl=(int)(provider.getT1Timeout()/100);
	    	scheduler.submitHeatbeat(this);
	    }
	    	
	    public long perform() {
	        if(ttl>0)
	        {
	        	ttl--;
	        	scheduler.submitHeatbeat(this);
	        	return 0;
	        }
	        	
	        try {
				lock.lock();
				//cancel it
				this.cancel();
				// start T1
				startT1();
				// send
				provider.send(t1t5encodedREL);
				// notify user
				ISUPTimeoutEvent timeoutEvent = new ISUPTimeoutEvent(provider, t1t5REL, ISUPTimeoutEvent.T1 , dpc);
				provider.deliver(timeoutEvent);
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				lock.unlock();
			}

            return 0;
        }
	}	

	private class TimerT5 extends Task  {
		    private int ttl;
		    	
		    public TimerT5(Scheduler scheduler)
		    {
		    	super(scheduler);
		    }
		    	
		    public int getQueueNumber()
		    {
		    	return scheduler.HEARTBEAT_QUEUE;
		    }
		    	
		    public void start()
		    {
		    	this.activate(true);
		    	ttl=(int)(provider.getT5Timeout()/100);
		    	scheduler.submitHeatbeat(this);
		    }
		    	
		    public long perform() {
		        if(ttl>0)
		        {
		        	ttl--;
		        	scheduler.submitHeatbeat(this);
		        	return 0;
		        }
		        	
		        try {
					lock.lock();
					// remove t5, its current runnable.
					cancelT1();
					//cancel it
					this.cancel();
					// restart T5
					startT5();
					// send
					ResetCircuitMessage rcm = provider.getMessageFactory().createRSC(cic);
					// avoid provider method, since we dont want other timer to be
					// setup.
					provider.sendMessage(rcm, dpc);

					// notify user
					ISUPTimeoutEvent timeoutEvent = new ISUPTimeoutEvent(provider, t1t5REL, ISUPTimeoutEvent.T5 , dpc);
					provider.deliver(timeoutEvent);
				} catch (Exception e) {
					e.printStackTrace();
				} finally {
					lock.unlock();
				}
		        
		        return 0;
	        }
	}
	 
	private class TimerT7 extends Task  {
	    private int ttl;
	    	
	    public TimerT7(Scheduler scheduler)
	    {
	    	super(scheduler);
	    }
	    	
	    public int getQueueNumber()
	    {
	    	return scheduler.HEARTBEAT_QUEUE;
	    }
	    	
	    public void start()
	    {
	    	this.activate(true);
	    	ttl=(int)(provider.getT7Timeout()/100);
	    	scheduler.submitHeatbeat(this);
	    }
	    	
	    public long perform() {
	        if(ttl>0)
	        {
	        	ttl--;
	        	scheduler.submitHeatbeat(this);
	        	return 0;
	        }
	        	
	        try {
				lock.lock();
				//cancel it
				this.cancel();
				// send REL
				ReleaseMessage rel = provider.getMessageFactory().createREL(cic);

				try {
					CauseIndicators ci = provider.getParameterFactory().createCauseIndicators();
					// TODO: add CI values
					rel.setCauseIndicators(ci);
					provider.sendMessage(rel, dpc);
				} catch (ParameterException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				// notify user
				ISUPTimeoutEvent timeoutEvent = new ISUPTimeoutEvent(provider, t7AddressMessage, ISUPTimeoutEvent.T7 , dpc);
				t7AddressMessage = null;
				provider.deliver(timeoutEvent);
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				lock.unlock();
			}
	        
	        return 0;
        }
	}
	
	private class TimerT12 extends Task  {
	    private int ttl;
	    	
	    public TimerT12(Scheduler scheduler)
	    {
	    	super(scheduler);
	    }
	    	
	    public int getQueueNumber()
	    {
	    	return scheduler.HEARTBEAT_QUEUE;
	    }
	    	
	    public void start()
	    {
	    	this.activate(true);
	    	ttl=(int)(provider.getT12Timeout()/100);
	    	scheduler.submitHeatbeat(this);
	    }
	    	
	    public long perform() {
	        if(ttl>0)
	        {
	        	ttl--;
	        	scheduler.submitHeatbeat(this);
	        	return 0;
	        }
	        	
	        try {
				lock.lock();
				//cancel it
				this.cancel();
				// start T12
				startT12();
				// send
				provider.send(t12t13encodedBLO);
				// notify user
				ISUPTimeoutEvent timeoutEvent = new ISUPTimeoutEvent(provider, t12t13BLO, ISUPTimeoutEvent.T12 , dpc);
				provider.deliver(timeoutEvent);
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				lock.unlock();
			}
	        
	        return 0;
        }
	}
	
	private class TimerT13 extends Task  {
	    private int ttl;
	    	
	    public TimerT13(Scheduler scheduler)
	    {
	    	super(scheduler);
	    }
	    	
	    public int getQueueNumber()
	    {
	    	return scheduler.HEARTBEAT_QUEUE;
	    }
	    	
	    public void start()
	    {
	    	this.activate(true);
	    	ttl=(int)(provider.getT13Timeout()/100);
	    	scheduler.submitHeatbeat(this);
	    }
	    	
	    public long perform() {
	        if(ttl>0)
	        {
	        	ttl--;
	        	scheduler.submitHeatbeat(this);
	        	return 0;
	        }
	        	
	        try {
				lock.lock();
				//cancel it
				this.cancel();
				// cancel T12
				cancelT12();
				// restart T13
				startT13();
				// send
				provider.send(t12t13encodedBLO);
				// notify user
				ISUPTimeoutEvent timeoutEvent = new ISUPTimeoutEvent(provider, t12t13BLO, ISUPTimeoutEvent.T13 , dpc);
				provider.deliver(timeoutEvent);
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				lock.unlock();
			}
	        
	        return 0;
        }
	}
	
	private class TimerT14 extends Task  {
	    private int ttl;
	    	
	    public TimerT14(Scheduler scheduler)
	    {
	    	super(scheduler);
	    }
	    	
	    public int getQueueNumber()
	    {
	    	return scheduler.HEARTBEAT_QUEUE;
	    }
	    	
	    public void start()
	    {
	    	this.activate(true);
	    	ttl=(int)(provider.getT14Timeout()/100);
	    	scheduler.submitHeatbeat(this);
	    }
	    	
	    public long perform() {
	        if(ttl>0)
	        {
	        	ttl--;
	        	scheduler.submitHeatbeat(this);
	        	return 0;
	        }
	        	
	        try {
				lock.lock();
				//cancel it
				this.cancel();
				// start T14				
				startT14();
				// send
				provider.send(t14t15encodedUBL);
				// notify user
				ISUPTimeoutEvent timeoutEvent = new ISUPTimeoutEvent(provider, t14t15UBL, ISUPTimeoutEvent.T14 , dpc);
				provider.deliver(timeoutEvent);
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				lock.unlock();
			}
	        
	        return 0;
        }
	}
	
	private class TimerT15 extends Task  {
	    private int ttl;
	    	
	    public TimerT15(Scheduler scheduler)
	    {
	    	super(scheduler);
	    }
	    	
	    public int getQueueNumber()
	    {
	    	return scheduler.HEARTBEAT_QUEUE;
	    }
	    	
	    public void start()
	    {
	    	this.activate(true);
	    	ttl=(int)(provider.getT15Timeout()/100);
	    	scheduler.submitHeatbeat(this);
	    }
	    	
	    public long perform() {
	        if(ttl>0)
	        {
	        	ttl--;
	        	scheduler.submitHeatbeat(this);
	        	return 0;
	        }
	        	
	        try {
				lock.lock();
				// remove t15, its current runnable.
				this.cancel();
				// cancel T14
				cancelT14();
				// start
				startT15();
				// send
				provider.send(t14t15encodedUBL);
				// notify user
				ISUPTimeoutEvent timeoutEvent = new ISUPTimeoutEvent(provider, t14t15UBL, ISUPTimeoutEvent.T15 , dpc);
				provider.deliver(timeoutEvent);
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				lock.unlock();
			}
	        
	        return 0;
        }
	}
	
	private class TimerT16 extends Task  {
	    private int ttl;
	    	
	    public TimerT16(Scheduler scheduler)
	    {
	    	super(scheduler);
	    }
	    	
	    public int getQueueNumber()
	    {
	    	return scheduler.HEARTBEAT_QUEUE;
	    }
	    	
	    public void start()
	    {
	    	this.activate(true);
	    	ttl=(int)(provider.getT16Timeout()/100);
	    	scheduler.submitHeatbeat(this);
	    }
	    	
	    public long perform() {
	        if(ttl>0)
	        {
	        	ttl--;
	        	scheduler.submitHeatbeat(this);
	        	return 0;
	        }
	        	
	        try {
				lock.lock();
				// remove us
				this.cancel();
				// start T14
				startT16();
				// send
				provider.send(t16t17encodedRSC);
				// notify user
				ISUPTimeoutEvent timeoutEvent = new ISUPTimeoutEvent(provider, t16t17RSC, ISUPTimeoutEvent.T16 , dpc);
				provider.deliver(timeoutEvent);
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				lock.unlock();
			}
	        
	        return 0;
        }
	}
	
	private class TimerT17 extends Task  {
	    private int ttl;
	    	
	    public TimerT17(Scheduler scheduler)
	    {
	    	super(scheduler);
	    }
	    	
	    public int getQueueNumber()
	    {
	    	return scheduler.HEARTBEAT_QUEUE;
	    }
	    	
	    public void start()
	    {
	    	this.activate(true);
	    	ttl=(int)(provider.getT17Timeout()/100);
	    	scheduler.submitHeatbeat(this);
	    }
	    	
	    public long perform() {
	        if(ttl>0)
	        {
	        	ttl--;
	        	scheduler.submitHeatbeat(this);
	        	return 0;
	        }
	        	
	        try {
				lock.lock();
				// remove t17, its current runnable.
				this.cancel();
				// cancel T16
				cancelT16();
				// restart T17
				startT17();
				// send
				provider.send(t16t17encodedRSC);
				// notify user
				ISUPTimeoutEvent timeoutEvent = new ISUPTimeoutEvent(provider, t16t17RSC, ISUPTimeoutEvent.T17 , dpc);
				provider.deliver(timeoutEvent);
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				lock.unlock();
			}
	        
	        return 0;
        }
	}
	
	private class TimerT18 extends Task  {
	    private int ttl;
	    	
	    public TimerT18(Scheduler scheduler)
	    {
	    	super(scheduler);
	    }
	    	
	    public int getQueueNumber()
	    {
	    	return scheduler.HEARTBEAT_QUEUE;
	    }
	    	
	    public void start()
	    {
	    	this.activate(true);
	    	ttl=(int)(provider.getT18Timeout()/100);
	    	scheduler.submitHeatbeat(this);
	    }
	    	
	    public long perform() {
	        if(ttl>0)
	        {
	        	ttl--;
	        	scheduler.submitHeatbeat(this);
	        	return 0;
	        }
	        	
	        try {
				lock.lock();
				// remove us
				this.cancel();
				// start T18
				startT18();
				// send
				provider.send(t18t19encodedCGB);
				// notify user
				ISUPTimeoutEvent timeoutEvent = new ISUPTimeoutEvent(provider, t18t19CGB, ISUPTimeoutEvent.T18 , dpc);
				provider.deliver(timeoutEvent);
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				lock.unlock();
			}
	        
	        return 0;
        }
	}
	
	private class TimerT19 extends Task  {
	    private int ttl;
	    	
	    public TimerT19(Scheduler scheduler)
	    {
	    	super(scheduler);
	    }
	    	
	    public int getQueueNumber()
	    {
	    	return scheduler.HEARTBEAT_QUEUE;
	    }
	    	
	    public void start()
	    {
	    	this.activate(true);
	    	ttl=(int)(provider.getT19Timeout()/100);
	    	scheduler.submitHeatbeat(this);
	    }
	    	
	    public long perform() {
	        if(ttl>0)
	        {
	        	ttl--;
	        	scheduler.submitHeatbeat(this);
	        	return 0;
	        }
	        	
	        try {
				lock.lock();
				// remove t19, its current runnable.
				this.cancel();
				// cancel T18
				cancelT18();
				// restart T19
				startT19();
				// send
				provider.send(t18t19encodedCGB);
				// notify user
				ISUPTimeoutEvent timeoutEvent = new ISUPTimeoutEvent(provider, t18t19CGB, ISUPTimeoutEvent.T19 , dpc);
				provider.deliver(timeoutEvent);
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				lock.unlock();
			}
	        
	        return 0;
        }
	}
	
	private class TimerT20 extends Task  {
	    private int ttl;
	    	
	    public TimerT20(Scheduler scheduler)
	    {
	    	super(scheduler);
	    }
	    	
	    public int getQueueNumber()
	    {
	    	return scheduler.HEARTBEAT_QUEUE;
	    }
	    	
	    public void start()
	    {
	    	this.activate(true);
	    	ttl=(int)(provider.getT20Timeout()/100);
	    	scheduler.submitHeatbeat(this);
	    }
	    	
	    public long perform() {
	        if(ttl>0)
	        {
	        	ttl--;
	        	scheduler.submitHeatbeat(this);
	        	return 0;
	        }
	        	
	        try {
				lock.lock();
				// remove us
				this.cancel();
				// start T20
				startT20();
				// send
				provider.send(t20t21encodedCGU);
				// notify user
				ISUPTimeoutEvent timeoutEvent = new ISUPTimeoutEvent(provider, t20t21CGU, ISUPTimeoutEvent.T20 , dpc);
				provider.deliver(timeoutEvent);
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				lock.unlock();
			}
	        
	        return 0;
        }
	}
	
	private class TimerT21 extends Task  {
	    private int ttl;
	    	
	    public TimerT21(Scheduler scheduler)
	    {
	    	super(scheduler);
	    }
	    	
	    public int getQueueNumber()
	    {
	    	return scheduler.HEARTBEAT_QUEUE;
	    }
	    	
	    public void start()
	    {
	    	this.activate(true);
	    	ttl=(int)(provider.getT21Timeout()/100);
	    	scheduler.submitHeatbeat(this);
	    }
	    	
	    public long perform() {
	        if(ttl>0)
	        {
	        	ttl--;
	        	scheduler.submitHeatbeat(this);
	        	return 0;
	        }
	        	
	        try {
				lock.lock();
				// remove t21, its current runnable.
				this.cancel();
				// cancel T20
				cancelT20();
				// restart T21
				startT21();
				// send
				provider.send(t20t21encodedCGU);
				// notify user
				ISUPTimeoutEvent timeoutEvent = new ISUPTimeoutEvent(provider, t20t21CGU, ISUPTimeoutEvent.T21 , dpc);
				provider.deliver(timeoutEvent);
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				lock.unlock();
			}
	        
	        return 0;
        }
	}
	
	private class TimerT22 extends Task  {
	    private int ttl;
	    	
	    public TimerT22(Scheduler scheduler)
	    {
	    	super(scheduler);
	    }
	    	
	    public int getQueueNumber()
	    {
	    	return scheduler.HEARTBEAT_QUEUE;
	    }
	    	
	    public void start()
	    {
	    	this.activate(true);
	    	ttl=(int)(provider.getT22Timeout()/100);
	    	scheduler.submitHeatbeat(this);
	    }
	    	
	    public long perform() {
	        if(ttl>0)
	        {
	        	ttl--;
	        	scheduler.submitHeatbeat(this);
	        	return 0;
	        }
	        	
	        try {
				lock.lock();
				// remove us
				this.cancel();
				// start T22
				startT22();
				// send
				provider.send(t22t23encodedGRS);
				// notify user
				ISUPTimeoutEvent timeoutEvent = new ISUPTimeoutEvent(provider, t22t23GRS, ISUPTimeoutEvent.T22 , dpc);
				provider.deliver(timeoutEvent);
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				lock.unlock();
			}
	        
	        return 0;
        }
	}
	
	private class TimerT23 extends Task  {
	    private int ttl;
	    	
	    public TimerT23(Scheduler scheduler)
	    {
	    	super(scheduler);
	    }
	    	
	    public int getQueueNumber()
	    {
	    	return scheduler.HEARTBEAT_QUEUE;
	    }
	    	
	    public void start()
	    {
	    	this.activate(true);
	    	ttl=(int)(provider.getT23Timeout()/100);
	    	scheduler.submitHeatbeat(this);
	    }
	    	
	    public long perform() {
	        if(ttl>0)
	        {
	        	ttl--;
	        	scheduler.submitHeatbeat(this);
	        	return 0;
	        }
	        	
	        try {
				lock.lock();
				// remove t23, its current runnable.
				this.cancel();
				// cancel T22
				cancelT22();
				// restart T23
				startT23();
				// send
				provider.send(t22t23encodedGRS);
				// notify user
				ISUPTimeoutEvent timeoutEvent = new ISUPTimeoutEvent(provider, t22t23GRS, ISUPTimeoutEvent.T23 , dpc);
				provider.deliver(timeoutEvent);
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				lock.unlock();
			}
	        
	        return 0;
        }
	}
	
	private class TimerT28 extends Task  {
	    private int ttl;
	    	
	    public TimerT28(Scheduler scheduler)
	    {
	    	super(scheduler);
	    }
	    	
	    public int getQueueNumber()
	    {
	    	return scheduler.HEARTBEAT_QUEUE;
	    }
	    	
	    public void start()
	    {
	    	this.activate(true);
	    	ttl=(int)(provider.getT28Timeout()/100);
	    	scheduler.submitHeatbeat(this);
	    }
	    	
	    public long perform() {
	        if(ttl>0)
	        {
	        	ttl--;
	        	scheduler.submitHeatbeat(this);
	        	return 0;
	        }
	        	
	        try {
				lock.lock();
				// remove us
				this.cancel();
				// notify user
				ISUPTimeoutEvent timeoutEvent = new ISUPTimeoutEvent(provider, t28CQM, ISUPTimeoutEvent.T28 , dpc);
				provider.deliver(timeoutEvent);
				t28CQM = null;
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				lock.unlock();
			}
	        
	        return 0;
        }
	}
	
	private class TimerT33 extends Task  {
	    private int ttl;
	    	
	    public TimerT33(Scheduler scheduler)
	    {
	    	super(scheduler);
	    }
	    	
	    public int getQueueNumber()
	    {
	    	return scheduler.HEARTBEAT_QUEUE;
	    }
	    	
	    public void start()
	    {
	    	this.activate(true);
	    	ttl=(int)(provider.getT33Timeout()/100);
	    	scheduler.submitHeatbeat(this);
	    }
	    	
	    public long perform() {
	        if(ttl>0)
	        {
	        	ttl--;
	        	scheduler.submitHeatbeat(this);
	        	return 0;
	        }
	        	
	        try {
				lock.lock();
				// remove us
				this.cancel();
				// notify user
				ISUPTimeoutEvent timeoutEvent = new ISUPTimeoutEvent(provider, t33INR, ISUPTimeoutEvent.T33 , dpc);
				provider.deliver(timeoutEvent);
				// FIXME: do this after call, to prevent send of another msg
				t33INR = null;

				// send REL
				ReleaseMessage rel = provider.getMessageFactory().createREL(cic);

				try {
					CauseIndicators ci = provider.getParameterFactory().createCauseIndicators();
					// TODO: add CI values
					rel.setCauseIndicators(ci);
					provider.sendMessage(rel,dpc);
				} catch (ParameterException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				lock.unlock();
			}
	        
	        return 0;
        }
	}
	
	/**
	 * 
	 */
	public void onStop() {
		try {
			lock.lock();
			cancelT1();
			cancelT5();
			cancelT12();
			cancelT13();
			cancelT14();
			cancelT15();
			cancelT16();
			cancelT17();
			cancelT18();
			cancelT19();
			cancelT20();
			cancelT21();
			cancelT22();
			cancelT23();
			cancelT28();
			cancelT33();
			
		} finally {
			lock.unlock();
		}
	}

}
